package server.services;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


import server.services.channels.IChannel;
import server.services.channels.ImpChannel;

/**
 * Internals server services
 * */
public class Server extends UnicastRemoteObject implements IServicesServeur{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private List<ImpChannel> channels = new ArrayList<ImpChannel>();
	private static int nbServer =1;
	private static List<Server> listeServer;
	private ArrayList<String> listePseudo;
	private static ArrayList<String[]> IpPort;
	
	public Server() throws RemoteException {
		listePseudo = new ArrayList<String>();
		//Channels by default
		channels.add(new ImpChannel("Sport", this));
		channels.add(new ImpChannel("Music", this));
		channels.add(new ImpChannel("Cinema", this));
		if(listeServer == null){
			listeServer = new ArrayList<Server>();
			IpPort = new ArrayList<String[]>();
		}
	}
	
	/**
	 * Define server with adress, port
	 * and distant interface IServicesServeur
	 * */
	/**
	 * Permet de definir un serveur avec son adresse et son port
	 * Creer egalement 4 autres serveurs avec la meme adresse en incrementant le port de 1
	 * Les autres serveurs sont utilises dans le cas de crash d'un serveur
	 * @param adress adresse IP du serveur
	 * @param port port du serveur
	 */
	public void startServer(String adress, String port){
	    	try {
	    		LocateRegistry.createRegistry(Integer.valueOf(port));
	    		
				Naming.bind("//"+adress+":"+port+"/IServicesServeur", this);
				String [] localIpP = {"localhost", ""+Integer.parseInt(port)};
				if(!listeServer.contains(this)){
					listeServer.add(this);
					IpPort.add(localIpP);
				}
				System.out.println(port);
				while(nbServer<5){
					listeServer.add(new Server());					
					String [] localIpPort = {"localhost", ""+(Integer.parseInt(port) + nbServer)};
					IpPort.add(localIpPort);
					nbServer++;
				}
				for(int i = 1; i<listeServer.size(); i++){
					listeServer.get(i).startServer(IpPort.get(i)[0], IpPort.get(i)[1]);
				}
				System.out.println("nb serveur : "+listeServer.size());
				System.out.println("nb port : "+IpPort.size());
				for(int i = 0; i<IpPort.size(); i++){
					System.out.println(IpPort.get(i)[1]);
				}
	    	} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (AlreadyBoundException e) {
				e.printStackTrace();
			} catch (RemoteException e) {
				
			}
	}
	
	/**
	 * Permet d'obtenir le nombre de clients connectes sur l'ensemble des serveurs
	 * @return le nombre de client sur l'ensemble des serveurs
	 */
	public int getNbClients(){
		int compteur = 0;
		for(Server server : listeServer){
			for(int i = 0; i<server.getChannels().size(); i++){
				compteur+=server.getChannels().get(i).getClientsName().size();
			}
		}
		return compteur;
	}
	
	/**
	 * Permet d'obtenir la liste des channels en cours d'utilisation
	 * @return la liste des channels
	 */
	public List<ImpChannel> getChannels(){
		return channels;
	}
	
	/**
	 * Stop server with adress, port
	 * and distant interface IServicesServeur
	 * */
	/**
	 * Permet de deconnecter un server
	 * @param adress adresse du serveur a stoper
	 * @param port port du serveur a stopper
	 */
	public void stopServer(String adress, String port){
    	try {
			Naming.unbind("//"+adress+":"+port+"/IServicesServeur");			
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (NotBoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Permet d'obtenir le channel dont le nom est place en parametre
	 * @param title titre du channel recherche
	 * @return le channel correspondant au title, null s'il n'existe pas
	 */
	@Override
	public IChannel getChannel(String title) throws RemoteException {
		
		for(ImpChannel channel : channels){
			if(channel.getTitle().equals(title)){
				return channel;
			}
		}
		channels.add(new ImpChannel(title, this));
		return channels.get(channels.size()-1);
	}

	/**
	 * Permet d'obtenir la liste de tous les noms des channels
	 * @return la liste des noms des channels
	 */
	@Override
	public List<String> getAllChanelsNames() throws RemoteException {
		Iterator<ImpChannel> iterChannels = channels.iterator();
		List<String> channelsNames = new ArrayList<String>();
		while(iterChannels.hasNext()){
			channelsNames.add(((ImpChannel)iterChannels.next()).getTitle());
		}
		return channelsNames;
	}
	
	/**
	 * Permet d'ajouter un channel a tous les serveurs
	 * @param nameChannel nom du channel a creer
	 */
	public void addChanel(String nameChannel) throws RemoteException{
		for(Server server : listeServer){
			boolean find = false;
			for(String channel : server.getAllChanelsNames()){
				if(channel.equals(nameChannel))
					find = true;
				
			}
			if(!find){
				server.getChannels().add(new ImpChannel(nameChannel, this));
			}
		}
	}

	/**
	 * Permet de supprimer un channel des serveurs
	 * @param nameChannel nom du channel a supprimer
	 */
	@Override
	public void removeChanel(String nameChannel) throws RemoteException {
		for(Server server : listeServer){
			boolean find = false;
			int channel = 0;
			for(int i = 0; i<server.getChannels().size() && !find; i++){
				if(server.getChannels().get(i).equals(nameChannel)){
					find = true;
					channel = i;
				}
			}
			if(find){
				server.getChannels().remove(channel);
			}
		}
		
	}
	
	/**
	 * Permet d'obtenir la liste des Pseudos connectes au serveur.
	 * @return la liste des pseudos
	 */
	public ArrayList<String> getListePseudo(){
		return listePseudo;
	}

	/**
	 * Permet de tester si un pseudo est deja utilise ou pas. Test effectue a la connexion du client
	 * @param pseudo pseudo a tester
	 * @return faux si le pseudo existe deja, vrai sinon
	 */
	@Override
	public boolean testPseudo(String pseudo) throws RemoteException {
		ArrayList<String> listePseudoChannel = new ArrayList<String>();
		for(Server server : listeServer){
			for(int i = 0; i<server.getChannels().size(); i++){
				for(int a = 0; a<server.getChannels().get(i).getClientsName().size(); a++){
					listePseudoChannel.add(server.getChannels().get(i).getClientsName().get(a));
				}
			}
		}
		
		//On regarde si un client present dans listePseudo s'est connecte a un canal entre temps
		for(int i = 0; i<listePseudoChannel.size(); i++){
			if(listePseudo.contains(listePseudoChannel.get(i))){
				listePseudo.remove(listePseudoChannel.get(i));
			}
		}

		//On regarde si le pseudo est utilise par un client connecte au serveur, mais sur aucun canal.
		if(listePseudoChannel.contains(pseudo)){
			return false;
		}else{
			for(Server server : listeServer){
				if(server.getListePseudo().contains(pseudo)){
					return false;
				}
			}
			listePseudo.add(pseudo);
			return true;
		}
	}
	
	/**
	 * Permet d'obtenir la liste des adresse/port de tous les serveurs disponibles
	 * @return la liste des serveurs disponibles
	 */
	public ArrayList<String[]> getIpPort() {
		return IpPort;
	}

	/**
	 * Permet d'envoyer un message a tous les serveurs sur le channel desire
	 * @param channel channel ou l'on doit envoyer le message
	 * @param message contenu du message
	 */
	@Override
	public void broadcast(String channel, String message) throws RemoteException {
		boolean deco = true;
		//Mise a jour de la liste des serveurs
		while(deco){
			deco = false;
			for(int i = 0; i<listeServer.size(); i++){
				try{
					Server tmp = listeServer.get(i);
					tmp.getChannels();
					
				}catch(Exception e){
					listeServer.remove(i);
					deco = true;
				}
			}
		}
		for(Server server : listeServer){
			server.getChannel(channel).broadcastChannel(message);
		}
	}
	
	/**
	 * Permet d'obtenir la liste des clients connectes a un channel
	 * @param channel nom du channel recherche
	 * @return la liste des clients du channel
	 */
	public ArrayList<String> getClientsName(String channel){
		ArrayList<String> listeClient = new ArrayList<String>();
		for(Server server : listeServer){
			try {
				listeClient.addAll(server.getChannel(channel).getClientsNameServer());
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return listeClient;
	}

}
